package se.thoka.twt.client.users;

import se.thoka.twt.client.utils.AsyncCallbackAdapter;
import se.thoka.twt.client.utils.TWTLogLevel;
import se.thoka.twt.client.utils.TWTLogger;

import com.google.gwt.event.shared.EventHandler;
import com.google.gwt.event.shared.GwtEvent.Type;
import com.google.gwt.event.shared.GwtEvent;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.event.shared.HasHandlers;
import com.google.gwt.event.shared.SimpleEventBus;
import com.google.gwt.user.client.rpc.AsyncCallback;

public abstract class UserClientDatastoreManager<U extends AbstractUser, US extends AbstractUserServiceAsync<U>> implements AbstractUserServiceAsync<U>, HasHandlers {

	private static UserClientDatastoreManager<?, ?> instance;
	private UserServiceAsyncProxy proxyService;
	private SimpleEventBus eventBus;
	private US service;
	private U user;
	
	public static UserClientDatastoreManager<?, ?> getInstance() {
		return instance;
	}
	
	protected UserClientDatastoreManager(US service) {
		if (instance != null) {
			throw new IllegalStateException("Only one user manager is allowed.");
		}
		instance = this;
		proxyService = new UserServiceAsyncProxy(service);
		eventBus = new SimpleEventBus();
		this.service = service;
	}
	
	public HandlerRegistration addUserLoggedInHandler(UserLoggedInHandler<U> handler) {
		return addHandler(handler, UserLoggedInEvent.TYPE);
	}
	
	public HandlerRegistration addUserLoggedOutHandler(UserLoggedOutHandler handler) {
		return addHandler(handler, UserLoggedOutEvent.TYPE);
	}
	
	public HandlerRegistration addUserUpdatedHandler(UserUpdatedHandler<U> handler) {
		return addHandler(handler, UserUpdatedEvent.TYPE);
	}

	@Override
	public void fireEvent(GwtEvent<?> event) {
		eventBus.fireEvent(event);
	}
	
	protected US getService() {
		return service;
	}
	
	public U getUser() {
		return user;
	}
	
	public String getUserId() {
		return user != null ? user.getId() : null;
	}
	
	@Override
	public void getCurrentUser(AsyncCallback<U> callback) {
		proxyService.getCurrentUser(callback);
	}
	
	@Override
	public void loginUser(String username, String password, AsyncCallback<U> callback) {
		proxyService.loginUser(username, password, callback);
	}
	
	public boolean isUserLoggedIn() {
		return user != null;
	}
	
	public void logoutUser() {
		logoutUser(new AsyncCallbackAdapter<Void>());
	}
	
	@Override
	public void logoutUser(AsyncCallback<Void> callback) {
		proxyService.logoutUser(callback);
	}
	
	protected <H extends EventHandler> HandlerRegistration addHandler(H handler, Type<H> type) {
		return eventBus.addHandler(type, handler);
	}
	
	private void setUser(U user) {
		U oldUser = this.user;
		this.user = user;
		TWTLogger.log(TWTLogLevel.DEBUG, "TWTUserManager#setUser(" + user + ") LoggedIn?" + isUserLoggedIn());
		if (oldUser == null && user != null) {
			// User logged in
			fireEvent(new UserLoggedInEvent<U>(user));
		} else if (oldUser != null && user == null) {
			// User logged out
			fireEvent(new UserLoggedOutEvent());
		} else if (oldUser != null && user != null) {
			if (oldUser.getId().equals(user.getId())) {
				// User updated
				fireEvent(new UserUpdatedEvent<U>(user));
			} else {
				fireEvent(new UserLoggedOutEvent());
				fireEvent(new UserLoggedInEvent<U>(user));
			}
		}
	}
	
	private class UserServiceAsyncProxy implements AbstractUserServiceAsync<U> {
		private US service;
		public UserServiceAsyncProxy(US service) {
			this.service = service;
		}
		@Override
		public void loginUser(String username, String password, AsyncCallback<U> callback) {
			service.loginUser(username, password, new UserCallback(callback));
		}
		@Override
		public void logoutUser(final AsyncCallback<Void> callback) {
			service.logoutUser(new AsyncCallback<Void>() {
				@Override
				public void onSuccess(Void result) {
					setUser(null);
					callback.onSuccess(result);
				}
				@Override
				public void onFailure(Throwable caught) {
					setUser(null);
					callback.onFailure(caught);
				}
			});
		}
		@Override
		public void getCurrentUser(AsyncCallback<U> callback) {
			service.getCurrentUser(new UserCallback(callback));
		}
	}
	
	private class UserCallback implements AsyncCallback<U> {
		private AsyncCallback<U> callback;
		public UserCallback(AsyncCallback<U> callback) {
			this.callback = callback;
		}
		@Override
		public void onSuccess(U result) {
			setUser(result);
			callback.onSuccess(result);
		}
		@Override
		public void onFailure(Throwable caught) {
			callback.onFailure(caught);
		}
	}
}
